package ru.ifmo.thesis.gp;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import org.jgap.BaseGeneticOperator;
import org.jgap.Chromosome;
import org.jgap.Configuration;
import org.jgap.FitnessEvaluator;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.IChromosome;
import org.jgap.InvalidConfigurationException;
import org.jgap.Population;
import org.jgap.RandomGenerator;
import org.jgap.event.GeneticEvent;
import org.jgap.event.GeneticEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.ifmo.thesis.domain.Job;
import ru.ifmo.thesis.util.Algorithms;

public class IterativeSOMutationOperator extends BaseGeneticOperator {

	private static final long serialVersionUID = 1L;
	
	private static final Logger myLogger = LoggerFactory.getLogger(IterativeSOMutationOperator.class);
	
	private final Writer writer;
	
	public IterativeSOMutationOperator(Configuration aConfiguration)
			throws InvalidConfigurationException {
		
		super(aConfiguration);
		
		try {
			writer = new BufferedWriter(
					new OutputStreamWriter(new FileOutputStream("iterative_evalanche_stats_c-pop5.txt", true)));
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		}
		
		aConfiguration.getEventManager().addEventListener(GeneticEvent.GENOTYPE_EVOLVED_EVENT,
				new GeneticEventListener() {
					@Override
					public void geneticEventFired(GeneticEvent event) {
						try {
//							System.err.println("FLUSHED");
							writer.flush();
						} catch (IOException e) {
							throw new RuntimeException(e);
						}
					}
		});
	}

	private int[] mutations;
	int evalancheSize = 1;

	@Override
	public void operate(Population aPopulation, List chromosomes) {
		//TODO need deep copy
		RandomGenerator rg = getConfiguration().getRandomGenerator();
		List<Chromosome> chromoCopy = Algorithms.deepCopyPopulation(
				(List<Chromosome>) aPopulation.getChromosomes());
		
		if (mutations == null) {
			mutations = new int[chromoCopy.size() + 500];
			initMutationVector();
		}
		
		FitnessEvaluator fEval = getConfiguration().getFitnessEvaluator();
		FitnessFunction fFunc = getConfiguration().getFitnessFunction();

		int worstIndividualIndex = getWorstIndex(chromoCopy, fEval, fFunc);
		int leftNgh = (worstIndividualIndex - 1 + chromoCopy.size()) % chromoCopy.size();
		int rightNgh = (worstIndividualIndex + 1) % chromoCopy.size();

		if (mutations[worstIndividualIndex] > 0) {
			evalancheSize++;
		} else {
			evalancheSize = 1;
			initMutationVector();
		}
		
		chromosomes.add(mutate(chromoCopy.get(worstIndividualIndex)));
		mutations[worstIndividualIndex]++;
	
		chromosomes.add(mutate(chromoCopy.get(leftNgh)));
		mutations[leftNgh]++;

		chromosomes.add(mutate(chromoCopy.get(rightNgh)));
		mutations[rightNgh]++;

		myLogger.debug("evalanche: " + evalancheSize);
		
		for (Chromosome c : chromoCopy) {
			chromosomes.add(mutate(c));
		}
		
		try {
			writer.write(evalancheSize + ",");
		} catch (IOException e) {
			throw new RuntimeException();
		}
	}


	private void initMutationVector() {
		for (int i = 0; i < mutations.length; i++) {
			mutations[i] = 0;
		}
	}

	private int getWorstIndex(List<Chromosome> chromoCopy,
			FitnessEvaluator fEval, FitnessFunction fFunc) {
		int worstIndividualIndex = 0;
		Chromosome worst = null;
		double worstFitness = 0;
		int i = 0;
		for (Chromosome c : chromoCopy) {
			if (worst == null) {
				worst = c;
				worstFitness = fFunc.getFitnessValue(worst);
				worstIndividualIndex = i;
			} else {
				double currentFitness = fFunc.getFitnessValue(c);
				if (fEval.isFitter(worstFitness, currentFitness)) {
					worst = c;
					worstFitness = fFunc.getFitnessValue(worst);
					worstIndividualIndex = i;
				}
			}
			i++;
		}
		return worstIndividualIndex;
	}

	private int getBestIndex(List<Chromosome> chromoCopy,
			final FitnessEvaluator fEval, FitnessFunction fFunc) {
		return getWorstIndex(chromoCopy, new FitnessEvaluator() {

			@Override
			public boolean isFitter(double a_fitness_value1,
					double a_fitness_value2) {
				return !fEval.isFitter(a_fitness_value1, a_fitness_value2);
			}

			@Override
			public boolean isFitter(IChromosome a_chrom1, IChromosome a_chrom2) {
				return !isFitter(a_chrom1, a_chrom2);
			}
			
		}, fFunc);
	}

	
	private Chromosome mutate(Chromosome c) {
		return applyMutation(c);
	}


	private Chromosome applyMutation(Chromosome c) {
		Gene[] gene = c.getGenes();
		
		//select 2 workers
		
		RandomGenerator rg = getConfiguration().getRandomGenerator();
		
		WorkPlanGene first = (WorkPlanGene) gene[rg.nextInt(gene.length)];
		WorkPlanGene second = (WorkPlanGene) gene[rg.nextInt(gene.length)];
		
		ArrayList<Job> firstAllele = (ArrayList) first.getAllele();
		ArrayList<Job> secondAllele = (ArrayList) second.getAllele();
		
		//choose between swaping and moving
		if (rg.nextBoolean() && !(firstAllele.isEmpty() || secondAllele.isEmpty())) {
			swapping(rg, firstAllele, secondAllele);
		} else {
			moving(rg, firstAllele, secondAllele);
		}
		
		first.setAllele(firstAllele);
		second.setAllele(secondAllele);
		
		return c;
	}


	private void moving(RandomGenerator rg, ArrayList<Job> firstAllele,
			ArrayList<Job> secondAllele) {
		if (firstAllele.isEmpty()) {
			if (!secondAllele.isEmpty()) {
				int movingIndex = rg.nextInt(secondAllele.size());
				firstAllele.add(secondAllele.remove(movingIndex));
			}
		} else {
			int movingIndex = rg.nextInt(firstAllele.size());
			Job value = firstAllele.remove(movingIndex);
			
			if (secondAllele.isEmpty()) {
				secondAllele.add(value);
			} else {
				int insertingIndex = rg.nextInt(secondAllele.size());
				secondAllele.add(insertingIndex, value);
			}
		}
	}


	private void swapping(RandomGenerator rg, ArrayList<Job> firstAllele,
			ArrayList<Job> secondAllele) {
		int swapIndex1 = rg.nextInt(firstAllele.size());
		int swapIndex2 = rg.nextInt(secondAllele.size());
		
		Job tmp = firstAllele.get(swapIndex1);
		firstAllele.set(swapIndex1, secondAllele.get(swapIndex2));
		secondAllele.set(swapIndex2, tmp);
	}


	@Override
	public int compareTo(Object o) {
		return 0;
	}
}
